﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MessageWorkflow.Graph;
using System.Threading;
using System.Collections.Specialized;
using MessageWorkflow.Handler;
using MessageWorkflow.Dispatch;

namespace MessageWorkflow
{
    public class WorkflowChannel : IDisposable
    {
        #region Default

        private static WorkflowChannel _defaultInstance = new WorkflowChannel();

        public static WorkflowChannel Default
        {
            get
            {
                return _defaultInstance;
            }
        }

        public static void OverrideDefault(WorkflowChannel newDefault)
        {
            if (newDefault == null)
                throw new ArgumentNullException("newDefault");

            _defaultInstance = newDefault;
        }

        #endregion // Default

        #region Private Members

        #endregion // Private Members

        #region Protected Properties

        protected IGraph<IWorkflowHandler> Graph { get; private set; }

        protected ReaderWriterLockSlim WorkflowHandlerGraphSynchronizer { get; private set; }

        #endregion // Protected Properties

        #region Constructor

        public WorkflowChannel()
        {
            Graph = new AdjacencyListGraph<IWorkflowHandler>();

            WorkflowHandlerGraphSynchronizer = new ReaderWriterLockSlim();
        }

        #endregion // Constructor

        #region Properties

        public IEnumerable<IWorkflowHandler> Handlers
        {
            get
            {
                WorkflowHandlerGraphSynchronizer.EnterReadLock();
                try
                {
                    // return a copy of the set of handlers to prevent synchronization conflicts
                    return Graph.Nodes.ToArray();
                }
                finally
                {
                    WorkflowHandlerGraphSynchronizer.ExitReadLock();
                }
            }
        }

        #endregion // Properties

        #region Methods

        public void Subscribe(WorkflowHandlerConfiguration configuration)
        {
            try
            {
                WorkflowHandlerGraphSynchronizer.EnterWriteLock();

                InternalSubscribe(configuration);
            }
            finally
            {
                WorkflowHandlerGraphSynchronizer.ExitWriteLock();
            }

        }

        public void UnsubscribeHandler(IWorkflowHandler handler)
        {
            if (handler == null)
                throw new ArgumentNullException("handler");

            WorkflowHandlerGraphSynchronizer.EnterWriteLock();
            try
            {
                Graph.Remove(handler);
            }
            finally
            {
                WorkflowHandlerGraphSynchronizer.ExitWriteLock();
            }

            OnHandlerUnsubscribed(new HandlerUnsubscribedEventArgs(handler));
        }

        public T Dispatch<T>(T message, IErrorStrategy errorStrategy)
        {
            IAsyncResult result = BeginDispatch(null, message, errorStrategy);

            // wait for completion
            result.AsyncWaitHandle.WaitOne();

            // this throws conditinoally
            EndDispatch(result);

            return message;
        }

        public IAsyncResult BeginDispatch<T>(AsyncCallback callback, 
            T message,
            IErrorStrategy errorStrategy)
        {
            if (errorStrategy == null)
                throw new ArgumentNullException("errorStrategy");

            WorkflowHandlerGraphSynchronizer.EnterReadLock();

            IGraph<IWorkflowHandler> workflowHandlerGraph;
            try
            {
                // construct a new graph based on the requested handlers for the message type specified
                workflowHandlerGraph = BuildSubGraph(message);
            }
            finally
            {
                WorkflowHandlerGraphSynchronizer.ExitReadLock();
            }

            Dispatcher dispatcher = new Dispatcher(workflowHandlerGraph,
                    errorStrategy,
                    callback,
                    message);

            return dispatcher.Begin();
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="Its common useage to call EndDispatch on the instance")]
        public void EndDispatch(IAsyncResult result)
        {
            AsyncResult<Dispatcher> asyncResult = result as AsyncResult<Dispatcher>;

            if (asyncResult == null)
                throw new ArgumentException("Expecting asyncResult of type AsyncResult<Dispatcher>");

            asyncResult.EndInvoke();
        }

        #endregion // Methods

        #region Private Helpers

       [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly",
            Justification = "Submember names of the configuration are valid")]
        private void InternalSubscribe(WorkflowHandlerConfiguration configuration)
        {
            if (configuration == null)
                throw new ArgumentNullException("configuration");

            try
            {
                if (!Graph.Nodes.Contains(configuration.Handler))
                {
                    Graph.Add(configuration.Handler);
                }

                if (configuration.IsPredecessorToAll && configuration.IsSuccessorToAll)
                {
                    throw new InvalidOperationException("Both IsPredecessorToAll and IsSuccessorToAll are set which can't be the case");
                }
                else if (configuration.IsPredecessorToAll)
                {
                    UpgradeToPredecessorFor(configuration.Handler, Graph.Nodes);
                }
                else if (configuration.IsSuccessorToAll)
                {
                    UpgradeToSuccessorFor(configuration.Handler, Graph.Nodes);
                }
                else
                {
                    if (configuration.Predecessors != null)
                    {
                        foreach (WorkflowHandlerConfiguration predecessorConfiguration in configuration.Predecessors)
                        {
                            InternalSubscribe(predecessorConfiguration);
                        }

                        UpgradeToSuccessorFor(configuration.Handler, configuration.Predecessors.Select(x => x.Handler));
                    }
                    else if (configuration.Successors != null)
                    {
                        foreach (WorkflowHandlerConfiguration predecessorConfiguration in configuration.Successors)
                        {
                            InternalSubscribe(predecessorConfiguration);
                        }

                        UpgradeToPredecessorFor(configuration.Handler, configuration.Successors.Select(x => x.Handler));
                    }
                }
            }
            catch
            {
                Graph.Remove(configuration.Handler);

                throw;
            }

            OnHandlerSubscribed(new HandlerSubscribedEventArgs(configuration));
        }

        private void UpgradeToPredecessorFor(IWorkflowHandler handler, IEnumerable<IWorkflowHandler> otherHandlers)
        {
            // Wrap this up in a set since the call desires that
            IEnumerable<IWorkflowHandler> predecessorSet = new[] { handler };

            otherHandlers = otherHandlers.Where(x => x != handler);

            foreach (IWorkflowHandler otherHandler in otherHandlers)
            {
                Graph.AddPredecessors(otherHandler, predecessorSet);
            }
        }

        private void UpgradeToSuccessorFor(IWorkflowHandler handler, IEnumerable<IWorkflowHandler> otherHandlers)
        {
            otherHandlers = otherHandlers.Where(x => x != handler);

            Graph.AddPredecessors(handler, otherHandlers);
        }

        #endregion // Private Helpers

        #region Protected Helpers

        /// <summary>
        /// Builds a graph of handlers for the event specified
        /// </summary>
        protected virtual IGraph<IWorkflowHandler> BuildSubGraph(object message)
        {
            if (message == null)
                throw new ArgumentNullException("message");

            Type eventType = message.GetType();

            // Get a subgraph of handlers that are related to this eventType
            IEnumerable<IWorkflowHandler> relevanthandlers = from h in Graph.Nodes
                                                             where h.CanHandle(eventType)
                                                             select h;

            return Graph.BuildSubGraph(relevanthandlers);
        }

        protected virtual void OnHandlerSubscribed(HandlerSubscribedEventArgs e)
        {
            EventHandler<HandlerSubscribedEventArgs> subscriptions = HandlerSubscribed;
            if (subscriptions != null)
            {
                subscriptions(this, e);
            }
        }

        protected virtual void OnHandlerUnsubscribed(HandlerUnsubscribedEventArgs e)
        {
            EventHandler<HandlerUnsubscribedEventArgs> subscriptions = HandlerUnsubscribed;
            if (subscriptions != null)
            {
                subscriptions(this, e);
            }
        }

        #endregion // Protected Helpers

        #region EventHandlers

        protected virtual void OnHandlerDependenciesChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e == null)
                throw new ArgumentNullException("e");

            IWorkflowHandler handler = (IWorkflowHandler)sender;

            try
            {
                Graph.AddPredecessors(handler, e.NewItems.Cast<IWorkflowHandler>());
                Graph.RemovePredecessors(handler, e.OldItems.Cast<IWorkflowHandler>());

                WorkflowHandlerGraphSynchronizer.EnterWriteLock();
            }
            finally
            {
                WorkflowHandlerGraphSynchronizer.ExitWriteLock();
            }
        }

        #endregion // EventHandlers

        #region Events

        public event EventHandler<HandlerSubscribedEventArgs> HandlerSubscribed;

        public event EventHandler<HandlerUnsubscribedEventArgs> HandlerUnsubscribed;

        #endregion // Events

        #region Cleanup

        ~WorkflowChannel()
        {
            // Finalizer calls Dispose(false)
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                WorkflowHandlerGraphSynchronizer.Dispose();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion // Cleanup
    }
}
